#include <cstdio>
#include <cstdlib>
//#include <tchar.h>

#include "opencv2/imgproc/imgproc.hpp"
#include "opencv2/highgui/highgui.hpp"
#include "opencv2/video/video.hpp"
#include <opencv2/gpu/gpu.hpp>


#include "VideoRead.h"

#include "FeatureComputer.h"

//#include "Classifier.h"

class LcInterface
{
public:

	LcInterface();
	void input( int argc, char * const argv[]);

//	LcClassifier*  getClassifier(){ return my_classifier;}

	LcVideoReadExt getTrainVideo();
	LcVideoReadExt getTestVideo();


	bool IF_TRAIN;
	bool IF_TEST;

	int train_f_start;
	int train_f_end;
	int train_f_rate;

	int test_f_start;
	int test_f_end;
	int test_f_rate;
	
	string addition_string;

	string root;

	string train_video_name;
	string test_video_name;

	string feature_code_string;

	//string classifier_name;

	void buildDatasetmap();

	map< string , Vec3i> data_map;

	bool setVideo( string name ,int & start, int & end , int & rate);
	void setClassifier();

	void init();
	

	//LcClassifier* my_classifier;
	


};

LcVideoReadExt LcInterface::getTrainVideo()
{
	LcVideoReadExt ans;
	ans.video_name = train_video_name;
	ans.f_start = train_f_start;
	ans.f_end = train_f_end;
	ans.f_rate = train_f_rate;
	ans.t_win = 0;
	ans.root = root;
	return ans;
}

LcVideoReadExt LcInterface::getTestVideo()
{
	LcVideoReadExt ans;
	ans.video_name = test_video_name;
	ans.f_start = test_f_start;
	ans.f_end = test_f_end;
	ans.f_rate = test_f_rate;
	ans.t_win = 0;
	ans.root = root;
	return ans;
}

//void LcInterface::setClassifier()
//{
//	if(classifier_name == "rdt")
//	{
//		my_classifier = new LcRandomTrees;
//	}
//}

void LcInterface::init()
{
	if(IF_TEST == false)
	{
		if(train_f_start<0) train_f_start = test_f_start;
		if(train_f_end<0) train_f_end = test_f_end;
		if(train_f_rate<0) train_f_rate = test_f_rate;
	}

	if(feature_code_string == "") feature_code_string = "l";

	//if(classifier_name == "" ) classifier_name = "rdt";
	//setClassifier();

	if(train_video_name == "" ){train_video_name = "GOPR2382_cab";}
	setVideo( train_video_name, train_f_start, train_f_end, train_f_rate);

	if(test_video_name == "" ){test_video_name = "GOPR2384_cab";}
	setVideo( test_video_name, test_f_start, test_f_end, test_f_rate);
}

LcInterface::LcInterface()
{
	train_f_start = -1;
	train_f_rate = -1;
	train_f_end = -1;

	test_f_start = -1;
	test_f_rate = -1;
	test_f_end = -1;

	root = "./../_FGS/";
	train_video_name = "";
	test_video_name = "";
	feature_code_string = "";
	addition_string = "";
	//classifier_name = "";

	IF_TEST = false;
	IF_TRAIN = false;

	buildDatasetmap();
}

void LcInterface::buildDatasetmap()
{
	data_map.clear();
	data_map.insert( pair< string, Vec3i>( "GOPR2382_cab", Vec3i(150,5300,50)) );
	data_map.insert( pair< string, Vec3i>( "GOPR2384_cab", Vec3i(150,11200,25)) );
}

bool LcInterface::setVideo( string name ,int & start, int & end , int & rate)
{
	map<string , Vec3i>::iterator iter = data_map.find( name );
	if( iter != data_map.end())
	{
		Vec3i temp = iter->second;
		if(start<0)start = temp[0];
		if(end<0)end = temp[1];
		if(rate<0)rate = temp[2];
		return true;
	}
	else
	{
		cout << "One of Video is not in default" << endl;
		return false;
	}
}

void LcInterface::input( int argc, char * const argv[])
{
	for(int ind = 1;ind < argc;ind++)
	{
		/*** Parse switches ***/
		if (argv[ind][0] == '-') {
			switch (argv[ind][1]) {
				case 'z':
					addition_string = string(argv[++ind]);
					break;
				case 'f':
					root = string(argv[++ind]);
					break;
				case 't':
					train_video_name = string(argv[++ind]);
					break;
				case 'p':
					test_video_name = string(argv[++ind]);
					break;
				case 'v':
					feature_code_string =  string(argv[++ind]);
					break;
				case 'a':
					IF_TRAIN = true;
					break;
				case 'b':
					IF_TEST = true;
					break;
				case 's':
					//only for predict video setting , for trainning video it's always solid with frame
					// unless it's no testing model then the test frame setting will be use
					// as training parameter
					test_f_start =  atoi(argv[++ind]);
					break;
				case 'e':
					test_f_end = atoi(argv[++ind]);
					break;
				case 'r':
					test_f_rate = atoi(argv[++ind]);
					break;
				//case 'c':
				//	classifier_name = string(argv[++ind]);
				//	break;

				default : printf("Unknown switch '%c'\n", argv[ind][1]);
				break;
			}
		}
	}

	init();
}


class VideoExtractor
{
public:
	int instance_limit;

	const char * feature_code_string;

	LcFeatureExtractor * my_extractor;

	void setFeatureCode( const char * n_code_string );

	LcVideoRead * my_reader;

	void setReader( LcVideoRead * n_reader );

	void work( Mat & feature, Mat & label);

	void init();

	void computeRate();

	float downsample_rate;
	int feature_dim;

	void append( Mat & feat    , Mat & lab,
				 Mat & feat_frm, Mat & lab_frm,
				 int & flag );
};

void VideoExtractor::computeRate()
{
	my_reader->reset();

	{//try to deal with first frame and see the size of descriptors.
		Mat frm, desc;
		my_reader->readNext( frm );
		my_extractor->veb = 0;
		my_extractor->work( frm, desc );

		int _rows = desc.rows;
		int _cols = desc.cols; feature_dim = _cols;
		int _frame_n = (my_reader->f_end - my_reader->f_start)/ my_reader->f_rate + 1;

		downsample_rate = float(instance_limit)/(float(_frame_n) * float(_rows));

		instance_limit = int(downsample_rate * _rows) * _frame_n;
	}
}

void VideoExtractor::init()
{
	computeRate();
}

void VideoExtractor::setFeatureCode( const char * n_code_string )
{
	feature_code_string = n_code_string;
	my_extractor = new LcFeatureExtractor;
	my_extractor->veb = 0;
	my_extractor->set_extractor( n_code_string );
}

void VideoExtractor::setReader( LcVideoRead * n_reader )
{
	my_reader = n_reader;
}

void VideoExtractor::append( Mat & feat    , Mat & lab,
				 Mat & feat_frm, Mat & lab_frm,
				 int & flag )
{
			

	int _rows = feat_frm.rows;

	{//swapping the matrix

		lc::swapMatrix( feat_frm,  feat_frm, lab_frm, lab_frm);
	}

	int _n = int( downsample_rate * _rows );
	int left = flag;
	int right = flag + _n;

	if(right > feat.rows ){right = feat.rows; _n = right - left;}

	feat_frm.rowRange(0,_n).copyTo(feat.rowRange(left, right));
	lab_frm.rowRange(0,_n).copyTo(lab.rowRange( left, right));

	flag = right;

}

void VideoExtractor::work( Mat & desc, Mat & lab)
{
	init();

	{

		int n_data_instance_now = 0;

		desc = Mat::zeros( instance_limit, feature_dim, 5);
		lab = Mat::zeros( instance_limit, 1 , 5);

		my_reader->reset();

		my_extractor->veb = 0;

		for(int j = 0 ; j < 3e5; j++)
		{
			Mat frm, frm_desc, gt, frm_lab;

			int statu = my_reader->readNext(frm);

			if(statu!=0) break;

			my_reader->getLabel( gt );

			int frame_id = my_reader->getf();

			cout << frame_id << " " << endl;

			my_extractor->work( frm, frm_desc , gt, frm_lab);

			append( desc, lab, frm_desc, frm_lab, n_data_instance_now);
			
		}

		if(n_data_instance_now<instance_limit)
		{
			cout << " warning! n_data_instance_now " << n_data_instance_now <<
				" != instance_limit" << instance_limit << " , fix that " << endl;

			desc = desc.rowRange(0,n_data_instance_now);
			lab = lab.rowRange(0,n_data_instance_now);
		}
	}
}

int main(int argc, char * const argv[])
{
	LcInterface my_setting;

	//char *const setting_string[] ={ "","-a","-v","lb","-t","GOPR2382_cab","-s", "550", "-e", "700", "-r","50","-f","./../../_FGS/"};
	//my_setting.input( 13 + 1 ,setting_string);

	my_setting.input( argc, argv);

	LcFeatureExtractor my_extractor;
	my_extractor.set_extractor( my_setting.feature_code_string );

	LcVideoReadExt my_reader = my_setting.getTrainVideo();

	VideoExtractor video_extractor;

	video_extractor.setFeatureCode( my_setting.feature_code_string.c_str() );
	video_extractor.setReader( &my_reader );

	int bag_n;
	{

		FILE * fid = fopen("bagging_config.txt","r");
		int instance_limit;
		fscanf(fid, "%d%d", &bag_n, &instance_limit);
		video_extractor.instance_limit = instance_limit*bag_n;
		fclose(fid);
	}

	{

		FILE * fid = fopen("random_waste.txt","r");
		int random_waste;
		fscanf(fid, "%d", &random_waste);
		int new_random_waste;
		for(int i = 0 ; i < random_waste ; i++)
		{
			new_random_waste = rand()%10000;
		}
		cout << new_random_waste << endl;
		random_waste = new_random_waste;

		fclose(fid);

		fid = fopen("random_waste.txt","w");

		fprintf(fid,"%d\n", random_waste);
		fclose(fid);

		
	}

	{
		Mat feature, label;
		video_extractor.work( feature, label);

		int step = feature.rows / bag_n;

		for(int i = 0 ; i <bag_n;i++)
		{

			Mat m_feature = feature.rowRange( step * i , step * ( i+1 ) ).t();

			Mat m_label = label.rowRange( step * i , step * ( i+1 ) ).t();

			char feature_name[400];
			char label_name[400];

			sprintf(feature_name,"temp_feature_%d_%s%s.bin", i, my_setting.feature_code_string.c_str(),my_setting.addition_string.c_str());
			sprintf(label_name,"temp_label_%d_%s%s.bin", i, my_setting.feature_code_string.c_str(),
			my_setting.addition_string.c_str());


			lc::LcMat2Bin( feature_name ,m_feature);
			lc::LcMat2Bin( label_name , m_label);
		}
	}

	return 0;
}
